#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#include <sys/poll.h>
#include "assert.h"
#define PORT 7000
#define QUEUE 20

#define SIZE 100

void Init_fd(struct pollfd *fds)   /* 初始化pollfd结构体数组 */
{
	for(int i = 0; i < SIZE; ++i)
	{
		fds[i].fd = -1;
		fds[i].events = 0;
		fds[i].revents = 0;
	}
}
 
void Insert_fd(struct pollfd *fds, int fd, short event)  /* 添加文件描述符及关注的事件 */
{
	for(int i = 0; i < SIZE; ++i)
	{
		if(-1 == fds[i].fd)
		{
			fds[i].fd = fd;
			fds[i].events = event;
			break;
		}
	}
}
 
void Delete_fd(struct pollfd *fds, int fd)  /* 清除文件描述符 */
{
	int i = 0;
	for(; i < SIZE; ++i)
	{
		if(fds[i].fd == fd)
		{
			fds[i].fd = -1;
			fds[i].events = 0;
			break;
		}
	}
}

 
int main()
{
    /*
    typedef struct{
        long int fds_bits[32];
    }fd_set;
    */
   //集合
    fd_set rfds;

    int ss = ::socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in server_sockaddr;
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    //printf("%d\n",INADDR_ANY);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    int socketoptvalue = 1;
        setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (const char*)&socketoptvalue, sizeof(int));

    /**************************
    struct sockaddr {  
     sa_family_t sin_family;//地址族
　　  char sa_data[14]; //14字节，包含套接字中的目标地址和端口信息               
　　 };
    struct sockaddr {  
        sa_family_t sin_family;//地址族
        uint16_t    sin_port;   //16位端口号
        struct in_addr sin_addr;  //32位IP地址
        char          sin_zero[8];  //不使用             
　　 };

    htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)

    inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值，用于sockaddr_in.sin_addr.s_addr。

    inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。
    ****************************/
    if(::bind(ss, (struct sockaddr* ) &server_sockaddr, sizeof(server_sockaddr))==-1)
    {
        perror("bind");
        exit(1);
    }
    /*****************************************************
     * #include <sys/socket.h>
        int listen( int sockfd, int backlog);
        sockfd：用于标识一个已捆绑未连接套接口的描述字。
        backlog：等待连接队列的最大长度。
     * ***************************************************/
    if(::listen(ss, QUEUE) == -1)
    {
        perror("listen");
        exit(1);
    }
 
    struct pollfd fds[SIZE];   /* pollfd结构体数组 */
	Init_fd(fds);   /* 初始化fds */
    Insert_fd(fds, ss, POLLIN);   /* 添加sockfd套接字 */
	



    while(1){

        /**********************************************************
         * #include<poll.h>
            int poll(struct pollfd *fds, nfds_t nfds, int timeout);
            1.fds参数是一个pollfd结构体数组，它指定所有用户感兴趣的文件描述符上的可读、可写和异常事件。pollfd结构体定义如下：\
            struct pollfd
            {
                int fd;  文件描述符 
                short events;   注册的事件 
                short revents;   实际发生的事件，由内核填充 
            };
            其中，events成员告诉poll所要监听fd上的哪些事件，它是一系列事件的按位或；revents成员则由内核修改，以反馈给应用程序fd上实际发生了哪些事件。
            2.nfds参数指定被监听事件集合fds的大小。其类型nfds_t的定义如下：
            typedef unsigned long int nfds_t;

            3.timeout参数指定poll的超时值，单位为毫秒。设置为-1时，poll调用一直阻塞，直到有事件发生。设置为0立即返回。返回值与select相同。
         * ********************************************************/
        int n = poll(fds, SIZE, -1);

        //int n = select(maxfd + 1, &readfds, NULL, NULL,NULL);
        if(n <= 0){
            if(n < 0){
                printf("select出错，客户端程序退出\n");
                break;
            }
            printf("no event\n");
            continue;
        }


        for(int i = 0; i < SIZE; ++i){
            int fd = fds[i].fd;
            if(fds[i].fd!=-1 ){
                if(fds[i].revents & POLLRDHUP)  /* 对方关闭连接 */
				{
                    printf("cli back %d\n", fd);
					close(fd);
					Delete_fd(fds, fd);
				}else if(fds[i].revents & POLLIN){
                    
                    if(fd == ss){
                        struct sockaddr_in client_addr;
                        socklen_t length = sizeof(client_addr);

                        int clifd = accept(ss, (struct sockaddr *)&client_addr, &length);
                        printf("clifd connect %d\n", clifd);
                        assert(clifd != -1);
                        Insert_fd(fds,clifd, POLLIN | POLLRDHUP);
                    }else{
                        char buffer[1024];   
                        memset(buffer, 0 ,sizeof(buffer));
                        int len = ::recv(fd, buffer, sizeof(buffer), 0);
                        if(len <= 0){
                            close(fd);
                            printf("cli back %d\n", fd);
                            Delete_fd(fds, fd);  //若事件为断开连接则删除文件描述符
                            
                            continue;

                        };
                        printf("recv from cli :%s", buffer);
                        int ret = send(fd, "ok", 2, 0);
                        if(ret < 0){
                            printf("send fail ret = %d\n",ret);
                        }
                    }
                }
            }
        }
    }


    ::close(ss);
    return 0;
}